﻿using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Media;

namespace DaveSexton.Labs
{
	/// <summary>
	/// Interaction logic for SilverlightLabHost.xaml
	/// </summary>
	public partial class SilverlightLabHost : UserControl, ILabHost
	{
		#region Public Properties
		public static readonly DependencyProperty HasSourceCodeProperty = DependencyProperty.Register("HasSourceCode", typeof(bool), typeof(SilverlightLabHost), new PropertyMetadata(false));
		public static readonly DependencyProperty SourceCodeProperty = DependencyProperty.Register("SourceCode", typeof(string), typeof(SilverlightLabHost), new PropertyMetadata(null));
		public static readonly DependencyProperty HasSourceXamlProperty = DependencyProperty.Register("HasSourceXaml", typeof(bool), typeof(SilverlightLabHost), new PropertyMetadata(false));
		public static readonly DependencyProperty SourceXamlProperty = DependencyProperty.Register("SourceXaml", typeof(string), typeof(SilverlightLabHost), new PropertyMetadata(null));
		public static readonly DependencyProperty CanChangeLabProperty = DependencyProperty.Register("CanChangeLab", typeof(bool), typeof(SilverlightLabHost), new PropertyMetadata(true));

		public bool HasSourceCode
		{
			get
			{
				object value = GetValue(HasSourceCodeProperty);

				Contract.Assume(value is bool);

				return (bool) value;
			}
			set
			{
				SetValue(HasSourceCodeProperty, value);
			}
		}

		public string SourceCode
		{
			get
			{
				return (string) GetValue(SourceCodeProperty);
			}
			set
			{
				SetValue(SourceCodeProperty, value);
			}
		}

		public bool HasSourceXaml
		{
			get
			{
				object value = GetValue(HasSourceXamlProperty);

				Contract.Assume(value is bool);

				return (bool) value;
			}
			set
			{
				SetValue(HasSourceXamlProperty, value);
			}
		}

		public string SourceXaml
		{
			get
			{
				return (string) GetValue(SourceXamlProperty);
			}
			set
			{
				SetValue(SourceXamlProperty, value);
			}
		}

		public bool CanChangeLab
		{
			get
			{
				object value = GetValue(CanChangeLabProperty);

				Contract.Assume(value is bool);

				return (bool) value;
			}
			set
			{
				SetValue(CanChangeLabProperty, value);
			}
		}

		public ObservableCollection<Lab> Labs
		{
			get
			{
				Contract.Ensures(Contract.Result<ObservableCollection<Lab>>() != null);

				return loadedLabs;
			}
		}

		public SolidColorBrush ContentBorderBrush
		{
			get
			{
				return contentBorderBrush;
			}
		}

		public string ApplicationTitle
		{
			get;
			set;
		}
		#endregion

		#region Private / Protected
		private static readonly Brush red = new SolidColorBrush(Colors.Red);
		private static readonly Brush yellowish = new SolidColorBrush(Color.FromArgb(0xFF, 0xB3, 0xA3, 0x00));
		private static readonly Brush limeish = new SolidColorBrush(Color.FromArgb(0xFF, 0x00, 0xC6, 0x00));
		private static readonly Brush magenta = new SolidColorBrush(Colors.Magenta);
		private static readonly Brush darkCyan = new SolidColorBrush(Color.FromArgb(0xFF, 0x00, 0x8B, 0x8B));
		private static readonly Brush darkGray = new SolidColorBrush(Colors.DarkGray);

		private readonly ObservableCollection<Lab> loadedLabs = new ObservableCollection<Lab>();
		private readonly SolidColorBrush contentBorderBrush = new SolidColorBrush(SystemColors.WindowFrameColor);
		private Lab defaultLab;
		private bool loaded;
		#endregion

		#region Constructors
		/// <summary>
		/// Constructs a new instance of the <see cref="SilverlightLabHost" /> class.
		/// </summary>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors",
			Justification = "Generated code in InitializeComponent causes ObjectInvariant to be invoked.")]
		public SilverlightLabHost()
		{
			Loaded += LabHost_Loaded;

			InitializeComponent();

			Contract.Assume(Output != null);
			Contract.Assume(Output.Blocks != null);

			Output.Blocks.Add(new Paragraph());

			Contract.Assume(LabList != null);

			LabList.SelectionChanged += LabList_SelectionChanged;
		}
		#endregion

		#region Methods
		[ContractInvariantMethod]
		private void ObjectInvariant()
		{
			Contract.Invariant(loadedLabs != null);
		}

		public void LoadLabs(LabCategory category)
		{
			loadedLabs.Clear();

			foreach (var lab in category.LabsRecursive.OrderBy(lab => lab.OrganizedName))
			{
				Contract.Assume(lab != null);

				var labToAdd = lab.IsProxy ? lab : SilverlightConsoleLabHost.Create(lab);

				Contract.Assume(labToAdd.IsProxy);

				var actual = labToAdd.ActualLab as SilverlightLab;

				if (actual != null)
				{
					actual.Host = this;
				}

				loadedLabs.Add(labToAdd);
			}
		}

		public void SetCurrentLab(Lab lab)
		{
			Contract.Assume(LabList != null);

			if (!lab.IsProxy)
			{
				lab = loadedLabs.FirstOrDefault(
					proxy =>
					{
						var host = proxy.ActualLab as SilverlightConsoleLabHost;

						return host != null && host.HostedLab == lab;
					});
			}

			if (loaded)
			{
				Dispatcher.BeginInvoke(() => LabList.SelectedItem = lab);
			}
			else
			{
				defaultLab = lab;
			}
		}

		public void Write(string message, TraceEventType eventType)
		{
			Contract.Assume(Output != null);

			Dispatcher.BeginInvoke(() =>
				{
					WriteInternal(message, eventType);

					ScrollOutputToEnd();
				});
		}

		public void WriteLine(string message, TraceEventType eventType)
		{
			Contract.Assume(Output != null);

			Dispatcher.BeginInvoke(() =>
				{
					WriteInternal(message, eventType, newLine: true);

					ScrollOutputToEnd();
				});
		}

		private void WriteInternal(string message, TraceEventType eventType, bool newLine = false)
		{
			Contract.Assume(Output != null);
			Contract.Assume(Output.Blocks != null);
			Contract.Assume(Output.Blocks.Count > 0);

			var paragraph = (Paragraph) Output.Blocks[Output.Blocks.Count - 1];

			Contract.Assume(paragraph != null);
			Contract.Assume(paragraph.Inlines != null);

			var foreground = GetForegroundBrush(eventType);

			var run = paragraph.Inlines.Count > 0
				? paragraph.Inlines[paragraph.Inlines.Count - 1] as Run
				: null;

			if (run != null && run.Foreground == foreground)
			{
				run.Text += message;
			}
			else
			{
				run = new Run()
				{
					Text = message ?? string.Empty
				};

				if (foreground != null)
				{
					run.Foreground = foreground;
				}

				paragraph.Inlines.Add(run);
			}

			if (newLine)
			{
				paragraph.Inlines.Add(new LineBreak());
			}
		}

		private void ScrollOutputToEnd()
		{
			Contract.Assume(Output != null);

			var blocks = Output.Blocks;

			Contract.Assume(blocks != null);
			Contract.Assume(blocks.Count > 0);

			var last = blocks[blocks.Count - 1];

			Contract.Assume(last != null);

			var end = last.ElementEnd;

			Contract.Assume(Output.Selection != null);

			Output.Selection.Select(end, end);
		}

		protected virtual Brush GetForegroundBrush(TraceEventType eventType)
		{
			switch (eventType)
			{
				case TraceEventType.Critical:
				case TraceEventType.Error:
					return red;
				case TraceEventType.Warning:
					return yellowish;
				case TraceEventType.Start:
				case TraceEventType.Resume:
					return limeish;
				case TraceEventType.Stop:
				case TraceEventType.Suspend:
					return magenta;
				case TraceEventType.Transfer:
					return darkCyan;
				case TraceEventType.Verbose:
					return darkGray;
				default:
					return null;
			}
		}

		public void Clear()
		{
			Dispatcher.BeginInvoke(() => ClearAll(Output));
		}

		private static void Copy(TextBox target)
		{
			Contract.Assume(target != null);

			try
			{
				Clipboard.SetText(target.SelectedText);
			}
			catch (System.Security.SecurityException)
			{
				// Occurred in testing when choosing "No" for the Silverlight user prompt to deny clipboard access.
			}
		}

		private static void Copy(RichTextBox target)
		{
			Contract.Assume(target != null);

			if (target.Selection != null)
			{
				try
				{
					var text = new StringBuilder(target.Selection.Text);

					for (int i = 0; i < text.Length; i++)
					{
						if (text[i] == '\n' && i > 0 && text[i - 1] != '\r')
						{
							text.Insert(i, new[] { '\r' });
						}
					}

					Clipboard.SetText(text.ToString());
				}
				catch (System.Security.SecurityException)
				{
					// Occurred in testing when choosing "No" for the Silverlight user prompt to deny clipboard access.
				}
			}
		}

		private static void SelectAll(TextBox target)
		{
			Contract.Assume(target != null);

			target.Focus();
			target.SelectAll();
		}

		private static void SelectAll(RichTextBox target)
		{
			Contract.Assume(target != null);

			target.Focus();
			target.SelectAll();
		}

		private static void ClearAll(TextBox target)
		{
			Contract.Assume(target != null);

			target.Text = string.Empty;
		}

		private static void ClearAll(RichTextBox target)
		{
			Contract.Assume(target != null);
			Contract.Assume(target.Blocks != null);

			target.Blocks.Clear();
			target.Blocks.Add(new Paragraph());
		}
		#endregion

		#region Event Handlers
		private void LabHost_Loaded(object sender, RoutedEventArgs e)
		{
			Contract.Assume(LabList != null);

			LabList.SelectedItem = defaultLab;

			loaded = true;
		}

		private void LabList_SelectionChanged(object sender, SelectionChangedEventArgs e)
		{
			Contract.Assume(LabList != null);

			var lab = (Lab) LabList.SelectedItem;

			bool hasSourceCode = lab != null && !string.IsNullOrWhiteSpace(lab.SourceCode);

			HasSourceCode = hasSourceCode;
			SourceCode = hasSourceCode ? lab.SourceCode : null;

			bool hasSourceXaml = lab != null && !string.IsNullOrWhiteSpace(lab.SourceXaml);

			HasSourceXaml = hasSourceXaml;
			SourceXaml = hasSourceXaml ? lab.SourceXaml : null;
		}

		public void CopyOutput(object sender, RoutedEventArgs e)
		{
			Copy(Output);
		}

		public void SelectAllOutput(object sender, RoutedEventArgs e)
		{
			SelectAll(Output);
		}

		public void ClearAllOutput(object sender, RoutedEventArgs e)
		{
			ClearAll(Output);
		}

		public void CopySourceCode(object sender, RoutedEventArgs e)
		{
			Copy(SourceCodeTextBox);
		}

		public void SelectAllSourceCode(object sender, RoutedEventArgs e)
		{
			SelectAll(SourceCodeTextBox);
		}

		public void CopyIsolatedSourceCode(object sender, RoutedEventArgs e)
		{
			Copy(IsolatedSourceCodeTextBox);
		}

		public void SelectAllIsolatedSourceCode(object sender, RoutedEventArgs e)
		{
			SelectAll(IsolatedSourceCodeTextBox);
		}

		public void CopySourceXaml(object sender, RoutedEventArgs e)
		{
			Copy(SourceXamlTextBox);
		}

		public void SelectAllSourceXaml(object sender, RoutedEventArgs e)
		{
			SelectAll(SourceXamlTextBox);
		}
		#endregion
	}
}